#include <linux/version.h>
#include <linux/kernel.h>
#include <linux/errno.h>
#include <linux/platform_device.h>
#include <linux/module.h>
#include <linux/moduleparam.h>
#include <linux/init.h>
#include <linux/printk.h>
#include <linux/cdev.h>
#include <linux/fs.h>
#include <linux/of.h>
#include <linux/slab.h>
#include <linux/of.h>
#include <linux/platform_device.h>
#include <linux/miscdevice.h>
#include <asm/uaccess.h>
#include "../dbg_out.h"

static int major = 0;
static int lastMinor = 0;
static struct class* cls = NULL;

static int plat_open(struct inode* i_node, struct file* f)
{
    DBG_INFO("plat open");
    return 0;
}

static int plat_close(struct inode* i_node, struct file* f)
{
    DBG_INFO("plat close");
    return 0;
}

static ssize_t plat_read(struct file* f, char __user* data, size_t size, loff_t* loff)
{
    DBG_INFO("plat read");
    return 0;
}

static ssize_t plat_write(struct file* f, const char __user* data, size_t size, loff_t* loff)
{
    DBG_INFO("plat write");
    return -1;
}

struct file_operations plat_fops = {
    .open = plat_open,
    .release = plat_close,
    .read = plat_read,
    .write = plat_write,
};

struct plat_dev_info
{
    struct device* dev;
    int arg;
};

void __device_dump(struct device* dev, const char* name)
{
    DBG_INFO("%s.parent: 0x%p", name, dev->parent);//not null
    if (dev->parent)
    {
        DBG_INFO("%s.parent->devt: %d", name, dev->parent->devt);//0
        DBG_INFO("%s.parent->init_name: 0x%p", name, dev->parent->init_name);//0
    }

    DBG_INFO("%s.p: 0x%p", name, dev->p);//not null
    DBG_INFO("%s.init_name: 0x%p", name, dev->init_name);//0

    DBG_INFO("%s.devt: MAJOR(%d), MINOR(%d)", name, MAJOR(dev->devt), MINOR(dev->devt));//0
    DBG_INFO("%s.type: 0x%p", name, dev->type);//0

    DBG_INFO("%s.bus: 0x%p", name, dev->bus);//not null
    if (dev->bus)
    {
        DBG_INFO("%s.bus->name: %s", name, dev->bus->name);//platform
        DBG_INFO("%s.bus->dev_name: 0x%p", name, dev->bus->dev_name);//0
    }

    DBG_INFO("%s.driver: 0x%p", name, dev->driver);//not null
    if (dev->driver)
    {
        DBG_INFO("%s.driver->name: %s", name, dev->driver->name);//plat_dev
    }

    DBG_INFO("%s.platform_data: 0x%p", name, dev->platform_data);//0
    DBG_INFO("%s.driver_data: 0x%p", name, dev->driver_data);//0

    DBG_INFO("%s.of_node: 0x%p", name, dev->of_node);//not null

    DBG_INFO("%s.fwnode: 0x%p", name, dev->fwnode);//not null

    DBG_INFO("%s.class: 0x%p", name, dev->class);//0
    if (dev->class)
    {
        DBG_INFO("%s.class->name: 0x%p", name, dev->class->name);//0
        if (dev->class->name)
            DBG_INFO("%s.class->name: %s", name, dev->class->name);//0
    }

    DBG_INFO("%s.iommu_group: 0x%p", name, dev->iommu_group);//0
    DBG_INFO("%s.iommu: 0x%p", name, dev->iommu);//0

    DBG_INFO("device info end\r\n");
}
#define device_dump(var) __device_dump(var, #var)

void __platform_device_dump(struct platform_device* plat_dev, const char* name)
{
    DBG_INFO("%s->name: %s", name, plat_dev->name);
    DBG_INFO("%s->id: %d", name, plat_dev->id);//-1
    DBG_INFO("%s->id_auto: %d", name, plat_dev->id_auto);//0
    DBG_INFO("%s->num_resources: %d", name, plat_dev->num_resources);//0
    DBG_INFO("%s->resource: 0x%p", name, plat_dev->resource);//0
    DBG_INFO("%s->id_entry: 0x%p", name, plat_dev->id_entry);//0
    DBG_INFO("%s->mfd_cell: 0x%p", name, plat_dev->mfd_cell);//0

    DBG_INFO("platform info end\r\n");
    __device_dump(&plat_dev->dev, "dev");
}
#define platform_device_dump(var) __platform_device_dump(var, #var);

int plat_probe(struct platform_device* plat_dev)
{
    DBG_INFO("plat probe");

    platform_device_dump(plat_dev);

    if (cls == NULL)
    {
        cls = class_create(THIS_MODULE, "plat_cls");
        ERROR_OUT_IF(IS_ERR(cls), NULL, -EINVAL, "class create error");
    }

    if (major == 0)
    {
        dev_t tmpdev = 0;
        alloc_chrdev_region(&tmpdev, 0, 255, "platform_test");
        ERROR_OUT_IF(tmpdev == 0, NULL, -ENOMEM, "major request fail");
        major = MAJOR(tmpdev);
    }

    // plat_dev->dev.class = cls;
    // plat_dev->dev.devt = MKDEV(major, lastMinor);
    struct cdev* pcdev = cdev_alloc();
    cdev_init(pcdev, &plat_fops);
    cdev_add(pcdev, MKDEV(major, lastMinor), 1);
    struct device* newDev = device_create(cls, NULL, MKDEV(major, lastMinor), NULL, "platDev%d", lastMinor);
    dev_set_drvdata(&plat_dev->dev, pcdev);
    // DBG_INFO("\r\n");
    // device_dump(newDev);

    lastMinor++;
    return 0;
}

int plat_remove(struct platform_device* plat_dev)
{
    DBG_INFO("plat_drv remove dev: %s", plat_dev->name);
    device_destroy(cls, plat_dev->dev.devt);
    cdev_del(dev_get_drvdata(&plat_dev->dev));
    return 0;
}

void plat_shutdown(struct platform_device* plat_dev)
{

}

int plat_suspend(struct platform_device* plat_dev, pm_message_t state)
{
    return 1;
}

int plat_resume(struct platform_device* plat_dev)
{
    return 1;
}

struct of_device_id id_table[] = {
    {.compatible = "zzh,platdev"},
    {}
};

struct platform_driver plat_drv = {
    .probe = plat_probe,
    .remove = plat_remove,

    .suspend = plat_suspend,
    .resume = plat_resume,

    .driver = {
        .name = "plat_dev",
        .of_match_table = id_table
    },
};

module_platform_driver(plat_drv);

MODULE_DESCRIPTION("test module");
MODULE_AUTHOR("zzh");
MODULE_LICENSE("GPL");
